/*
 * CopyRight(C) Kenbin
 */

#include "lsg_config.h"

LSG::CBuddyFile::CBuddyFile(const char *filename)
{
    m_buddybit_obj = NULL;

    m_errid = 0;

    /* get filename that no path prefix */
    char *p = strrchr((char *)filename, '/');
    if(p) {
	p ++;
    } else {
	p = (char *)filename;
    }

    char s[32] = {0};
    int file_seq;

    ParseString(s, sizeof(s), p, 0, NULL, '.');
    m_block_size = atoi(s);

    ParseString(s, sizeof(s), p, 1, NULL, '.');
    m_block_count = atoi(s);

    ParseString(s, sizeof(s), p, 2, NULL, '.');
    file_seq = atoi(s);

    m_buddybit_size = GetBuddyBitSize(m_block_count);

    /* if set m_errid, caller MUST handle error */
    if(m_block_size <= 0) {
	m_errid = 1;
    } else if(m_buddybit_size <= 0) {
	m_errid = 2;
    } else if(0x7FFFFFFF / m_block_count < m_block_size) {
	m_errid = 3;
    } else if(file_seq < 0) {
	m_errid = 4;
    }

    if(m_errid != 0) {
	return;
    }

    m_fd = open(filename, O_RDWR | O_NONBLOCK | O_CREAT, FILE_ACESS_AUTH);
    if(m_fd < 0)
    {
	m_errid = 5;
	m_errno = errno;
	return;
    }

    int file_size = lseek(m_fd, 0, SEEK_END);
    if(file_size < HEADBLOCK_SIZE + m_buddybit_size)
    {
	if(create_buddybit() < 0)
	{
	    m_errid = 6;
	    return;
	}
    }
    else
    {
	if(load_buddybit() < 0)
	{
	    m_errid = 7;
	    return;
	}
    }
}

LSG::CBuddyFile::CBuddyFile(const char *filename, int block_size, int block_count)
{
    m_buddybit_obj = NULL;

    m_errid = 0;

    m_block_size = block_size;
    m_block_count = block_count;
    m_buddybit_size = GetBuddyBitSize(m_block_count);

    /* if set m_errid, caller MUST handle error */
    if(m_block_size <= 0) {
	m_errid = 1;
    } else if(m_buddybit_size <= 0) {
	m_errid = 2;
    } else if(0x7FFFFFFF / m_block_count < m_block_size) {
	m_errid = 3;
    }

    if(m_errid != 0) {
	return;
    }

    m_fd = open(filename, O_RDWR | O_NONBLOCK | O_CREAT, FILE_ACESS_AUTH);
    if(m_fd < 0)
    {
	m_errid = 5;
	m_errno = errno;
	return;
    }

    int file_size = lseek(m_fd, 0, SEEK_END);
    if(file_size < HEADBLOCK_SIZE + m_buddybit_size)
    {
	if(create_buddybit() < 0)
	{
	    m_errid = 6;
	    return;
	}
    }
    else
    {
	if(load_buddybit() < 0)
	{
	    m_errid = 7;
	    return;
	}
    }
}

LSG::CBuddyFile::~CBuddyFile()
{
    if(m_fd >= 0)
    {
	flush_headblock();
	close(m_fd);
	m_fd = -1;
    }

    if(m_buddybit_obj)
    {
	delete m_buddybit_obj;
	m_buddybit_obj = NULL;
    }
}

int LSG::CBuddyFile::Save(char *data, int data_len, int reserve)
{
    if(data_len <= 0) {
	return LSG_INVALID_DATA_LEN;
    }

    int count = (data_len + m_block_size - 1 + RESERVE_SIZE) / m_block_size;
    int byte_offset = 0;
    int bytes = 0;

    int buddybit_offset = m_buddybit_obj->Alloc(count, &byte_offset, &bytes);
    if(buddybit_offset < 0) {
	return buddybit_offset;
    }

    /* update <head_block> */
    int e = flush_headblock();
    if(e < 0) {
	return e;
    }

    /* update <buddybit> */
    e = lseek(m_fd, HEADBLOCK_SIZE + byte_offset, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    e = write(m_fd, m_buddybit_obj->BuddyBit() + byte_offset, bytes);
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    /* update <data_blocks> */
    off_t data_offset = (unsigned)HEADBLOCK_SIZE 
	+ (unsigned)m_buddybit_size 
	+ (unsigned)buddybit_offset * (unsigned)m_block_size;

    e = lseek(m_fd, data_offset, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    char rv[RESERVE_SIZE];
    memset(rv, 0, sizeof(RESERVE_SIZE));
    int *p = (int *)rv;
    *p = htonl(data_len);
    *(p + 1) = htonl(reserve);

    e = write(m_fd, rv, sizeof(rv));
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    e = write(m_fd, data, data_len);
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    return buddybit_offset;
}

int LSG::CBuddyFile::Update(int buddybit_offset, int byte_offset, 
	const char *data, int data_len, int reserve)
{
    if(buddybit_offset < 0 || buddybit_offset > m_block_count) { 
	return LSG_INVALID_BUDDYBIT_OFFSET;
    } else if(byte_offset < 0) {
	return LSG_INVALID_BYTE_OFFSET;
    }

    if(buddybit_offset == m_block_count) {
	return 0;
    }

    int slotitem_size = m_buddybit_obj->SlotItemSize(buddybit_offset);
    if(slotitem_size < 0) {
	return slotitem_size;
    }

    int expect_offset = byte_offset + data_len + RESERVE_SIZE;
    if(expect_offset > slotitem_size * m_block_size) {
	return LSG_DATA_TOO_LONG;
    }

    /* update <data_blocks> */
    off_t data_offset = (unsigned)HEADBLOCK_SIZE 
	+ (unsigned)m_buddybit_size 
	+ (unsigned)buddybit_offset * (unsigned)m_block_size;

    int e = lseek(m_fd, data_offset, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    char rv[RESERVE_SIZE];
    memset(rv, 0, sizeof(rv));
    int *p = (int *)rv;
    *p = htonl(byte_offset + data_len);
    *(p + 1) = htonl(reserve);

    e = write(m_fd, rv, sizeof(rv));
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    data_offset = (unsigned)HEADBLOCK_SIZE 
	+ (unsigned)m_buddybit_size 
	+ (unsigned)buddybit_offset * (unsigned)m_block_size 
	+ RESERVE_SIZE 
	+ byte_offset;

    e = lseek(m_fd, data_offset, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    if(write(m_fd, data, data_len) != data_len) {
	return LSG_WRITE_ERR;
    } else {
	return 0;
    }
}

int LSG::CBuddyFile::Read(int buddybit_offset, 
	char **data, int *data_len, int *reserve)
{
    *data_len = 0;
    if(buddybit_offset < 0 || buddybit_offset > m_block_count) {
	return LSG_INVALID_BUDDYBIT_OFFSET;
    } else if(buddybit_offset == m_block_count) {
	return 0;
    }

    int slotitem_size = m_buddybit_obj->SlotItemSize(buddybit_offset);
    if(slotitem_size < 0) {
	return slotitem_size;
    }

    int read_size = slotitem_size * m_block_size;
    char *pp = new char[read_size];
    if(pp == NULL) {
	return LSG_ALLOC_MEMORY_ERR;
    }

    memset(pp, 0, sizeof(read_size));

    /* read <data_blocks> */
    off_t data_offset = (unsigned)HEADBLOCK_SIZE 
	+ (unsigned)m_buddybit_size 
	+ (unsigned)buddybit_offset * (unsigned)m_block_size;

    int e = lseek(m_fd, data_offset, SEEK_SET);
    if(e < 0)
    {
	delete []pp;
	return LSG_LSEEK_ERR;
    }

    e = read(m_fd, pp, read_size);
    if(e < 0)
    {
	delete []pp;
	return LSG_READ_ERR;
    }

    int *pi = (int *)pp;
    *pi = ntohl(*pi);

    if(*pi < 0 || *pi > read_size - RESERVE_SIZE)
    {
	delete []pp;
	return LSG_INVALID_DATA_LEN;
    }

    *data_len = *pi;
    if(reserve != NULL) {
	*reserve = ntohl(*(pi + 1));
    }
    memmove(pp, pp+RESERVE_SIZE, *data_len);
    memset(pp + *data_len, 0, read_size - *data_len);

    *data = pp;

    return 0;
}

int LSG::CBuddyFile::Delete(int buddybit_offset)
{
    if(buddybit_offset < 0 || buddybit_offset > m_block_count) {
	return LSG_INVALID_BUDDYBIT_OFFSET;
    } else if(buddybit_offset == m_block_count) {
	return 0;
    }

    int byte_offset = 0, bytes = 0;
    m_buddybit_obj->Free(buddybit_offset, &byte_offset, &bytes);
    if(bytes <= 0) {
	return LSG_BUDDYBIT_FREE_FAIL;
    }

    /* update <head_block> */
    int e = flush_headblock();
    if(e < 0) {
	return e;
    }

    /* update <buddybit> */
    e = lseek(m_fd, HEADBLOCK_SIZE + byte_offset, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    e = write(m_fd, m_buddybit_obj->BuddyBit() + byte_offset, bytes);
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    return 0;
}

int LSG::CBuddyFile::create_buddybit()
{
    m_buddybit_obj = new CBuddyBit(NULL, m_buddybit_size);
    if(m_buddybit_obj == NULL) {
	return LSG_CREATE_OBJECT_ERR;
    }

    int e = ftruncate(m_fd, 0);
    if(e < 0) {
	return LSG_TRUNCATE_ERR;
    }

    e = ftruncate(m_fd, HEADBLOCK_SIZE + m_buddybit_size);
    if(e < 0) {
	return LSG_TRUNCATE_ERR;
    }

    return flush_headblock();
}

int LSG::CBuddyFile::load_buddybit()
{
    char *p = new char[m_buddybit_size];
    if(p == NULL) {
	return LSG_ALLOC_MEMORY_ERR;
    }

    int e = lseek(m_fd, HEADBLOCK_SIZE, SEEK_SET);
    if(e < 0)
    {
	delete []p;
	return LSG_LSEEK_ERR;
    }

    e = read(m_fd, p, m_buddybit_size);
    if(e < 0)
    {
	delete []p;
	return LSG_READ_ERR;
    }

    m_buddybit_obj = new CBuddyBit(p, m_buddybit_size);
    if(m_buddybit_obj == NULL)
    {
	delete []p;
	return LSG_CREATE_OBJECT_ERR;
    }

    delete []p;
    return flush_headblock();
}

int LSG::CBuddyFile::flush_headblock()
{
    char rv[32];

    int *alloc_count = (int *)rv;
    int *block_size = alloc_count + 1;
    int *block_count = alloc_count + 2;

    *alloc_count = m_buddybit_obj->Alloced();
    if(*alloc_count < 0) {
	return *alloc_count;
    }

    *block_size = m_block_size;
    *block_count = m_block_count;

    *alloc_count = htonl(*alloc_count);
    *block_size = htonl(*block_size);
    *block_count = htonl(*block_count);

    int e = lseek(m_fd, 0, SEEK_SET);
    if(e < 0) {
	return LSG_LSEEK_ERR;
    }

    e = write(m_fd, rv, sizeof(int) * 3);
    if(e < 0) {
	return LSG_WRITE_ERR;
    }

    return 0;
}
